package com.yunchao.feature.base.presentation.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlin.properties.Delegates

abstract class BaseViewModel<State : BaseState, Action : BaseAction<State>, Effect : BaseEffect>(
    initialState: State
) : ViewModel() {

    private val _uiStateFlow = MutableStateFlow(initialState)
    val uiStateFlow = _uiStateFlow.asStateFlow()

    /**
     * effect用作 事件带来的副作用，通常是 一次性事件 且 一对一的订阅关系
     * 例如：弹Toast、导航Fragment等
     */
    private val _effect: Channel<Effect> = Channel()

    val uiEffectFlow = _effect.receiveAsFlow()

    private var stateTimeTravelDebugger: StateTimeTravelDebugger? = null

    init {
        stateTimeTravelDebugger = StateTimeTravelDebugger(this::class.java.simpleName)
    }

    // Delegate handles state event deduplication (multiple states of the same type holding the same data
    // will not be emitted multiple times to UI)
    private var state by Delegates.observable(initialState) { _, old, new ->
        if (old != new) {
            viewModelScope.launch {
                _uiStateFlow.value = new
            }

            stateTimeTravelDebugger?.apply {
                addStateTransition(old, new)
                logLast()
            }
        }
    }

    protected fun sendAction(action: Action) {
        stateTimeTravelDebugger?.addAction(action)
        state = action.reduce(state)
    }

    /**
     * 设置新的副作用
     */
    protected fun postSideEffect(newEffect: Effect) {
        viewModelScope.launch {
            _effect.send(newEffect)
        }
    }

    /**
     * 安全启动协程的通用方法
     */
    protected fun safeLaunch(
        onStart: () -> Unit = {},
        onFinally: () -> Unit = {},
        onError: (Throwable) -> Unit = {},
        block: suspend () -> Unit
    ) {
        viewModelScope.launch {
            try {
                onStart()
                block()
            } catch (e: Exception) {
                onError(e)
            } finally {
                onFinally()
            }
        }
    }
}
